#!/usr/bin/env perl

use warnings;
use strict;
use LWP::Simple;
use Getopt::Long;
use File::Temp qw/ tempfile tempdir /;
use Cwd;
use Data::Dumper;
use File::Copy::Recursive qw(rcopy);
use File::Path;
use Config::Auto;

#FIXME:
# change to global usage of config
# able to update form milestone in cache
# remove dependency on File::Temp, File::Copy::Recursive

############################ Constants
my $DEBUG_LOG_LEVEL= 3;
my $NORMAL_LOG_LEVEL= 2;
my $QUIET_LOG_LEVEL= 0;

############################ Globals
my $log_level= $NORMAL_LOG_LEVEL;

############################ Prototypes
sub merge_defaults($$);     #works
sub milestone_in_cache($$); #works 
sub ensure_cache_dir($);    #works
sub cache_touch($$);        #works
sub cache_evict($);         #works
sub cache_add_from_checkout($$);
sub run;                    #works
sub interpret_branch($$);
sub is_milestone($);        #works (copied)
sub latest_milestone($);    
sub grab_tag_list();        #works (copied)
sub update_to($$$);
sub ensure_workdir();        #works
sub untar_milestone($$);  
sub download_from_go_oo($$); #works
sub logger;                  #works

############################ Subs			  
sub merge_defaults($$)
{
    my $defaults= shift;
    my $config= shift;
    
    while ((my $key, my $value) = each %$defaults)
    {
	$config->{$key} = $value unless exists $config->{$key};
    }
}

sub milestone_in_cache($$)
{
    my $config= shift;
    my $milestone= shift;
    my $dir= $config->{'cache_dir'};

    opendir(DIR, $dir) || die "can't opendir $dir: $!";
    my @entries= readdir(DIR);
    closedir(DIR);
    foreach (@entries)
    {
	if (/$milestone\.tar\.bz2/)
	{
	    logger "$milestone is a cache hit";
	    return 1;
	}
    }
    return 0;
}

sub ensure_cache_dir($)
{
    my $config= shift;
    unless (-d $config->{'cache_dir'})
    {
	mkdir $config->{'cache_dir'};
    }
}

sub cache_touch($$)
{
    my $config= shift;
    my $milestone= shift;
    my $filename= $config->{'cache_dir'} . "$milestone.tar.bz2";
    die "can't write to: $filename!!" unless -w $filename;
    system("touch", $filename);
}

sub cache_evict($)
{
    my $config= shift;
    my $dir= $config->{'cache_dir'};
    opendir(DIR, $dir) || die "can't opendir $dir: $!";
    my @entries= readdir(DIR);
    closedir(DIR);
    my %meta;
    foreach my $file (@entries)
    {
	if ($file =~ /\.tar\.bz2/)
	{
	    my @stat_values= stat $dir . $file;
	    $meta{$file}= $stat_values[9];
	}
    }
    logger "Cache data: " . Dumper(\%meta), $DEBUG_LOG_LEVEL;
    if ((keys %meta) == $config->{'cache_size'})
    {
	my $evictee = (sort { $meta{$a} <=> $meta{$b} } keys %meta)[0];
	logger "evicting $evictee from cache";
	unlink $dir . $evictee;
    }
}

sub cache_add_from_checkout($$)
{
    my $config= shift;
    my $milestone= shift;

    my $cwd = &Cwd::cwd();
    chdir('workdir');
    logger "grabbing $milestone from cvs";
    run('cvs', '-d', $config->{'cvs_root'}, 
	$config->{'cvs_options'}, 'co', '-r', $milestone, 'OpenOffice2');
    my @files= glob '*';
    logger "tarballing $milestone for cache...";
    run('tar', 'cjf', $config->{'cache_dir'} . "$milestone.tar.bz2", @files);
    logger "done!";
    chdir $cwd;
}

sub run
{
    logger "system(\"" . join ('", "', @_) . "\");\n", $DEBUG_LOG_LEVEL;
    die "can't run $_[0]!!\n" unless !system(@_);
}

sub interpret_branch($$)
{
    my $name= shift;
    my $cws_list= shift;
    my $milestone;
    my $cws= "none"; #special case

    if ($name eq 'HEAD' || $name eq '')
    {
	$milestone= latest_milestone('SRC680');
    }
    elsif(is_milestone($name))
    {
	$milestone= $name;
    }
    else
    {
	die "cws not found!!\n" unless $cws_list->{$name};
	$cws= $name;
	$milestone= $cws_list->{$name}->{'base'};
    }
    return ($milestone, $cws);
}

sub grab_tag_list()
{
    my $url= 'http://go-oo.org/tinderbox/tags/tag-list';
        
    my $response = get($url) || die "Couldn't get $url\n";
    my %h;
    foreach(split /\n/, $response)
    {
	unless (/\#.*/ || /^\s*$/)
	{
	    (my $name, my $base, my $tag, my $dirs) = split / : /, $_;
	    
	    $h{$name}= 
	    {
		'base' => $base,
		'tag'  => $tag,
		'dirs' => [split / /, $dirs]
		};
	}
    }
    return \%h;
}

sub latest_milestone($)
{
    my $master= shift;
    my $url = 'http://go-oo.org/tinderbox/tags/tag-latest-master-list';
    
    my $response = get($url) || die "Couldn't get $url\n";
    foreach(split /\n/, $response)
    {
	unless (/\#.*/)
	{
	    (my $name, my $base, my $tag, my $dirs) = split / : /, $_;
	    return $name if $name =~ /$master/;
	}
    }
    die "couldn't find latest milestone!\n";
}

sub is_milestone($)
{
    my $name= shift;
    return 1 if ($name =~ /\w+680_m\d+/);
    return 0;
}

sub update_to($$$)
{
    my $config= shift;
    my $cws_list= shift;
    my $name= shift;
    my $cwd = &Cwd::cwd();
    my $cws_tag= $cws_list->{$name}->{'tag'};
    logger "updating to $cws_tag...";
    chdir 'workdir';
    foreach (@{$cws_list->{$name}->{'dirs'}})
    {
	run('cvs', "-d", $config->{'cvs_root'}, $config->{'cvs_options'}, 'co', "-Pr", "$cws_tag", "$_");
    }
    logger "done!";
    chdir $cwd;
}

sub ensure_workdir()
{
    logger "removing workdir if exists ...";
    rmtree 'workdir' if -d 'workdir';
    mkdir 'workdir';
    logger "done!";
}

sub untar_milestone($$)
{
    my $config= shift;
    my $milestone= shift;

    my $filename= $config->{'cache_dir'} . $milestone . ".tar.bz2";
    
    my $cwd = &Cwd::cwd();		
    die "can't find tarball!" unless -f $filename;
    chdir 'workdir';
    run('tar', 'xjf', $filename);
    chdir $cwd;
}

sub download_from_go_oo($$)
{
    my $config= shift;
    my $milestone= shift;
    (my $master, my $number)= split /_/, $milestone;
    my $base_url= "http://go-oo.org/packages/$master/";
    my $base_file= lc $master . "-$number";
    my @files=("binfilter.tar.bz2",
	       "core.tar.bz2",
	       "lang.tar.bz2",
	       "sdk_oo.tar.bz2",
	       "system.tar.bz2");
    my $cwd = &Cwd::cwd();
    my $tempdir = tempdir ( $milestone . "-XXXX", DIR => $cwd, CLEANUP => 1);
    logger "trying to find $milestone tarballs at go-oo...";
    chdir($tempdir);
    foreach my $file (@files)
    {
	logger "getting $base_url$base_file-$file...";
	unless (is_success(getstore("$base_url$base_file-$file", $file)))
	{
	    chdir $cwd;
	    logger "failing";
	    return 0;
	}
	logger "done!";
    }
    logger "success!";
    foreach my $file (@files)
    {
	chdir $cwd;
	chdir 'workdir';
	logger "unpacking $file ...";
	run('tar', 'xjf', "$tempdir/$file");
	logger "done!";
    }
    my @dirs= glob "$base_file/*";
    foreach my $dir (@dirs) { run("mv", $dir, ".") }
    rmtree "$base_file";
    @dirs= glob '*';
    logger "packing $milestone for cache ...";
    run('tar', 'cjf', $config->{'cache_dir'} . "$milestone.tar.bz2", @dirs);
    logger "done!";
    chdir $cwd;
    return 1;
}

sub logger
{
    my $msg= shift;
    my $level = shift || $log_level;
    
    if ($level <= $log_level)
    {
	if ($msg eq "done!" and ($level < $DEBUG_LOG_LEVEL))
	{
	    print STDERR $msg . " (at " . time() . ")\n";
	}
	else
	{
	    print STDERR "[" . time() . "] - " . $msg;
	    print STDERR "\n" unless ($msg=~/.*\.\.\.$/ && $log_level < $DEBUG_LOG_LEVEL);
	}
    }
}

##################  Main

my $help_option;
my $branch= '';
GetOptions('help'        => \$help_option,
	   'branch:s'    => \$branch);

if ($help_option)
{
    print STDERR "Usage:\n";
    print STDERR "\t$0 [--branch=<branch>]\n";
    print STDERR "\twhere <branch> is a cws name or milestone name\n";
    print STDERR "Examples:\n";
    print STDERR "\"$0 --branch=SRC680_m212\"  get SRC680 milestone 212\n";
    print STDERR "\"$0 --branch=swwarnings\"  get swwarnings cws\n";
    print STDERR "\"$0 --branch=HEAD\"  get latest SRC680 milestone\n";
    print STDERR "\"$0\"  get latest SRC680 milestone\n\n\n";
    exit 1;
}

my %defaults= ( 
		'cache_size' => 4,
		'cvs_root' => ':pserver:anoncvs@anoncvs.services.openoffice.org:/cvs',
		'cvs_options' => "-z6",
		'log_level' => $NORMAL_LOG_LEVEL,
		'cache_dir' => &Cwd::cwd() . '/tarballs/'
		);
logger "$0 started";
my $config= Config::Auto::parse("$0.config", format => 'equal');
my $cws_list= grab_tag_list;
merge_defaults(\%defaults, $config);
$log_level= $config->{'log_level'};
logger "config_values: " . Dumper($config), $DEBUG_LOG_LEVEL;
ensure_cache_dir($config);
(my $milestone, my $cws)= interpret_branch($branch, $cws_list);

logger "milestone= $milestone";
logger "cws= $cws";

ensure_workdir();
if (milestone_in_cache($config, $milestone))
{
    cache_touch($config, $milestone);
    untar_milestone($config, $milestone);
}
else
{
    cache_evict($config);
    unless (download_from_go_oo($config, $milestone))
    {
	cache_add_from_checkout($config, $milestone);
    } 
}
update_to($config, $cws_list, $cws) if $cws ne "none";
logger "finished"
